// SpeedOrderDoc.cpp : implementation of the CSpeedOrderDoc class
//

#include "stdafx.h"
#include "SpeedOrder.h"

#include "SpeedOrderDoc.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
static const CString sFileDate = __TIMESTAMP__;

/////////////////////////////////////////////////////////////////////////////
// CSpeedOrderDoc

IMPLEMENT_DYNCREATE(CSpeedOrderDoc, CDocument)

BEGIN_MESSAGE_MAP(CSpeedOrderDoc, CDocument)
	//{{AFX_MSG_MAP(CSpeedOrderDoc)
		// NOTE - the ClassWizard will add and remove mapping macros here.
		//    DO NOT EDIT what you see in these blocks of generated code!
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

BEGIN_DISPATCH_MAP(CSpeedOrderDoc, CDocument)
	//{{AFX_DISPATCH_MAP(CSpeedOrderDoc)
		// NOTE - the ClassWizard will add and remove mapping macros here.
		//      DO NOT EDIT what you see in these blocks of generated code!
	//}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()

// Note: we add support for IID_ISpeedOrder to support typesafe binding
//  from VBA.  This IID must match the GUID that is attached to the 
//  dispinterface in the .ODL file.

// {56350BC9-2835-421F-9DB5-A1C7625DF24A}
static const IID IID_ISpeedOrder =
{ 0x56350bc9, 0x2835, 0x421f, { 0x9d, 0xb5, 0xa1, 0xc7, 0x62, 0x5d, 0xf2, 0x4a } };

BEGIN_INTERFACE_MAP(CSpeedOrderDoc, CDocument)
	INTERFACE_PART(CSpeedOrderDoc, IID_ISpeedOrder, Dispatch)
END_INTERFACE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CSpeedOrderDoc construction/destruction



CSpeedOrderDoc::CSpeedOrderDoc()
	: m_MonoWaves(m_PriceNodes)
	, m_PriceNodeInterval(0)
	, m_PriceNodesPerInterval(1)
	, m_PriceNodeIntervalRaw(0)
{
	m_PriceNodesRaw.reserve(20000);
	m_PriceNodes.reserve(20000);

	EnableAutomation();

	AfxOleLockApp();
}

CSpeedOrderDoc::~CSpeedOrderDoc()
{
	AfxOleUnlockApp();
}

BOOL CSpeedOrderDoc::OnNewDocument()
{
	if (!CDocument::OnNewDocument())
		return FALSE;

	// TODO: add reinitialization code here
	// (SDI documents will reuse this document)

	return TRUE;
}



/////////////////////////////////////////////////////////////////////////////
// CSpeedOrderDoc serialization

static CString RemoveSuffix(CString& s)
{
	CString f;
	int n = s.ReverseFind('.');
	if (n >= 0)
	{
		f = s.Mid(n + 1);
		f.MakeLower();
		s.Delete(n + 1, s.GetLength() - n - 1);
	}
	return f;
}

void CSpeedOrderDoc::Serialize(CArchive& ar)
{
	int n;
	CString s, x;
	if (ar.IsStoring())
	{
		s = ar.GetFile()->GetFileTitle();
		x = RemoveSuffix(s);
		if (x == "raw")
		{
			ar.Flush();
			ExportFileRaw(ar.GetFile(), m_InstrumentName);
		}
		else
			if (x != "eea")
			{
				ar.Flush();
				ExportFile(ar.GetFile(), m_InstrumentName);
			}
			else
			{
				ar << sFileDate;
				n = m_PriceNodesRaw.size();
				ar << n;
				ar << m_PriceNodeIntervalRaw;
				if (n > 0)
					ar.Write(&m_PriceNodesRaw[0], sizeof(PriceNode) * n);

				n = m_PriceNodes.size();
				ar << n;
				ar << m_PriceNodeInterval;
				ar << m_PriceNodesPerInterval;
				ar.Write(&m_PriceNodes[0], sizeof(PriceNode) * n);

				n = m_MonoWaves.size();
				ar << n;
				ar.Write(&m_MonoWaves[0], sizeof(WaveNode) * n);
			}
	}
	else  // not storing; read file
	{
		s = ar.GetFile()->GetFileTitle();
		x = RemoveSuffix(s);
		if (x != "eea")
		{
			ar.Flush();
			ImportFile(ar.GetFile());
		}
		else
		{
			ar >> s;
			if (s != sFileDate)
				throw new CArchiveException(CArchiveException::badSchema, ar.GetFile()->GetFilePath());

			ar >> n;
			ar >> m_PriceNodeIntervalRaw;
			if (n > 0)
			{
				m_PriceNodesRaw.resize(n);
				ar.Read(&m_PriceNodesRaw[0], sizeof(PriceNode) * n);
			}

			ar >> n;
			ar >> m_PriceNodeInterval;
			ar >> m_PriceNodesPerInterval;
			m_PriceNodes.resize(n);
			ar.Read(&m_PriceNodes[0], sizeof(PriceNode) * n);

			ar >> n;
			m_MonoWaves.resize(n);
			ar.Read(&m_MonoWaves[0], sizeof(WaveNode) * n);
			for (int i = 0; i < n; i++)
				m_MonoWaves[i].mpPriceNodeVec = &m_PriceNodes;
		}
	}
}

/////////////////////////////////////////////////////////////////////////////
// CSpeedOrderDoc diagnostics

#ifdef _DEBUG
void CSpeedOrderDoc::AssertValid() const
{
	CDocument::AssertValid();
}

void CSpeedOrderDoc::Dump(CDumpContext& dc) const
{
	CDocument::Dump(dc);
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CSpeedOrderDoc commands


BOOL CSpeedOrderDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
	if (!CDocument::OnOpenDocument(lpszPathName))
		return FALSE;

	// TODO:  Add your specialized creation code here
	SetPathName(lpszPathName, FALSE);
	m_InstrumentName = GetTitle();
	int n = m_InstrumentName.ReverseFind('.');
	if (n >= 0)
		m_InstrumentName = m_InstrumentName.Left(n);

	// Initialize members of the document object that 
	// aren't initialized when the document is 
	// serialized from disk...
	if (m_MonoWaves.size() == 0)
	{
#ifdef _DEBUG
		ETime t1;
		ETime t2;
		t1.Change(2006, 7, 28, 17, 0, 0);
		t2.Change(2006, 7, 31, 9, 5, 0);
		int d1 = t1.Diff(t2, 1, WEEKDAY_ONLY);
		int d2 = t2.Diff(t1, 1, WEEKDAY_ONLY);
		d1 = t1.Diff(t2, 1);
		d2 = t2.Diff(t1, 1);
		d1 = t1 - t2;
		d2 = t2 - t1;
		d1 = t1.Diff(t2);
		d2 = t2.Diff(t1);
#endif
		m_PriceNodeIntervalRaw = EW_GetAveragePeriod(m_PriceNodesRaw);
		// TODO: give the user more control over how to process the raw input data
		// for now use some simple heuristics
		m_PriceNodeInterval = DAYSECONDS;
		if (m_PriceNodeIntervalRaw < 3600)
		{
			m_PriceNodesPerInterval = 2;
			EW_MergeDataIntoPeriods(m_PriceNodesRaw, m_PriceNodeInterval, m_PriceNodesPerInterval, m_PriceNodes);
		}
		else
		{
			m_PriceNodes = m_PriceNodesRaw;
			m_PriceNodesRaw.clear();
		}

		EW_BuildMonoWaves(m_PriceNodes, m_MonoWaves);
		// WriteMonoWaves();
		EW_ProcessRulesOfRetracement(m_MonoWaves);
		// Write Out The MonoWaveArray To A File
		WriteMonoWaves();

#ifdef _DEBUG // INCLUDE_SPEEDY_CHANGES
		// find fast moves
		n = EW_FindSpeedyChanges(m_PriceNodesRaw, 1.0, 0.5, m_SignalMatches);
		n = EW_FindSpeedyChanges(m_PriceNodesRaw, -1.0, -0.5, m_SignalMatches);
		TRACE("EEDoc: found total of %d changes of %g %%/h\n", n, 0.5);
		CalcNodeSetProfit(m_SignalMatches, true);

		n = EW_FindSpeedyChanges2(m_PriceNodesRaw, 15 * 60, 2.0, m_SignalMatches2up);
		n = EW_FindSpeedyChanges2(m_PriceNodesRaw, 15 * 60, -2.0, m_SignalMatches2down);
#endif // INCLUDE_SPEEDY_CHANGES

#ifdef INCLUDE_SPEEDY_CHANGES2_SEARCH
		// find fast moves
		for (size_t minTime = 10 * 60; minTime < 30 * 60; minTime += 60)
		{
			for (double speed = 0.5; speed < 3.0; speed += 0.1)
			{
				m_SignalMatches2up.clear();
				m_SignalMatches2down.clear();
				n = EW_FindSpeedyChanges2(m_PriceNodesRaw, minTime, speed, m_SignalMatches2up);
				n = EW_FindSpeedyChanges2(m_PriceNodesRaw, minTime, -speed, m_SignalMatches2down);
				double profitSum = CalcNodeSetProfit(m_SignalMatches2up, false);
				TRACE("minTime %d, speed %g: signals up %d, mean profit %g\n", minTime, speed, n, profitSum);
				profitSum = -CalcNodeSetProfit(m_SignalMatches2down, false);
				TRACE("minTime %d, speed %g: signals down %d, mean profit %g\n", minTime, speed, n, profitSum);
			}
		} // for over minTime
#endif // INCLUDE_SPEEDY_CHANGES

#ifdef _DEBUG // INCLUDE_LOCAL_EXTREMES
		n = EW_FindLocalExtremes(m_PriceNodesRaw, 60, 60, m_LocalExtremes);
		TRACE("EEDoc: found total of %d local extremes\n", n);

		//tNodeSet::const_iterator it = m_LocalExtremes.begin();
		//for (; it != m_LocalExtremes.end(); it++)
		//{
		//  const PriceNode& p1 = *(*it);
		//  TRACE("%s low/hi %g\n", p1.mDate.Format(), p1.mLast);
		//}
#endif // INCLUDE_LOCAL_EXTREMES

#ifdef CALCULATE_SPEED_CHANGES
		tNodePairSet matches;
		for (double speed = 0.1; speed < 4.01; speed += 0.1)
		{
			matches.clear();
			n = EW_FindSpeedyChanges(m_PriceNodesRaw, 1.0, speed, matches);
			TRACE("EEDoc: found %d changes of %g %%/h\n", n, speed);
			matches.clear();
			n = EW_FindSpeedyChanges(m_PriceNodesRaw, -1.0, -speed, matches);
			TRACE("EEDoc: found %d changes of %g %%/h\n", n, -speed);
		}
#endif // CALCULATE_SPEED_CHANGES
	}
	return TRUE;
}

void CSpeedOrderDoc::SetPathName(LPCTSTR lpszPathName, BOOL bAddToMRU)
{
	CString p = lpszPathName;
	CString s = RemoveSuffix(p);
	p += "eea";
	CDocument::SetPathName(p, bAddToMRU);
}

void CSpeedOrderDoc::ImportFile(CFile* fp)
{
	size_t fileLength = fp->GetLength();
	if (fileLength > 0)
	{
		char* loadBuffer = (char*)malloc(fileLength + 1);
		fp->Read(loadBuffer, fileLength);
		loadBuffer[fileLength] = '\0';

		ParseBufferTicker(loadBuffer, fileLength);
		free(loadBuffer);
	}
}

void CSpeedOrderDoc::ExportFile(CFile* fp, const char* isin)
{
	CString s = "time;isin;high;low;last;vol$;open\r\n";
	fp->Write(s, s.GetLength());

	char buf[100];
	PriceNodeVec::const_iterator it = m_PriceNodes.begin();
	while (it != m_PriceNodes.end())
	{
		const PriceNode& n = *it++;

		sprintf(buf, "%s;%s;%g;%g;%g;%g;%g\r\n",
			n.mDate.Format("%Y%m%d"),
			isin,
			n.mHigh,
			n.mLow,
			n.mPrice,
			n.mVolume,
			n.mLast);
		fp->Write(buf, strlen(buf));
	} // while
}

void CSpeedOrderDoc::ExportFileRaw(CFile* fp, const char* isin)
{
	char buf[100];
	PriceNodeVec::const_iterator it = m_PriceNodesRaw.begin();
	int lastMinute = 9999;
	float dayBegin = 0.0f;
	while (it != m_PriceNodesRaw.end())
	{
		const PriceNode& n = *it++;
		CTime t(n.mDate.GetYear(), n.mDate.GetMonth(), n.mDate.GetDay(), n.mDate.GetHour(), n.mDate.GetMinute(), n.mDate.GetSecond());
		sprintf(buf, "%d %g\r\n",
			(time_t)t.GetTime(), // n.mDate.Format("%Y%m%d%H%M%S"),
			n.mPrice);
		fp->Write(buf, strlen(buf));
		/*
		// print with minute-within-tradingday
		int minute = (n.mDate.GetSecondsWithinDay() - ETime::m_TradingDayOpen) / 60;
		if (minute < lastMinute)
		dayBegin = n.mPrice;
		if ((minute % 5) == 0 && dayBegin > 10.0)
		{
		float v = 100.0f * (n.mPrice - dayBegin) / dayBegin;
		sprintf(buf, "%d %g\r\n", minute/5, v);
		fp->Write(buf, strlen(buf));
		}
		lastMinute = minute;
		*/
	} // while
}


typedef enum { Invalid, Date, Open, High, Low, Close, Volume, Ignore } tFields;

static int ParseLineIntoNode(char* pLine, char delimiter, const tFields* pLineTemplate, const char* pDateTemplate, PriceNode& node)
{
	char* pFields[20] = { NULL };
	char* p = pLine;

	int numFields = 0;
	pFields[numFields++] = pLine;
	for (; *p; p++)
	{
		if (*p == delimiter)
		{
			*p = NULL;
			pFields[numFields++] = p + 1;
		}
	}

	int d, i;
	for (i = 0; i < numFields && pLineTemplate[i] != Invalid; i++)
	{
		switch (pLineTemplate[i])
		{
		case Date:
			if (!node.mDate.Unformat(pFields[i], pDateTemplate))
				return 0;
			d = node.mDate.GetDayOfWeek();
#ifdef _DEBUG
			if (d < 1 || d > 5)
				TRACE("Found non week-day date at %s\n", pFields[i]);
#endif
			break;
		case Open:
			node.mOpen = atof(pFields[i]);
			break;
		case High:
			node.mHigh = atof(pFields[i]);
			break;
		case Low:
			node.mLow = atof(pFields[i]);
			break;
		case Close:
			node.mLast = atof(pFields[i]);
			break;
		case Volume:
			node.mVolume = atof(pFields[i]);
			break;
		}
	}
	return i;
}

const tFields YahooTemplate[] = { Date, Open, High, Low, Close, Volume, Invalid };
const char YahooDateTemplate1[] = "%d-%b-%y";
const char YahooDateTemplate2[] = "%m/%d/%y";
const tFields HK_DayTemplate[] = { Date, Ignore, High, Low, Close, Volume, Ignore, Open, Invalid };
const char HK_DateTemplateDay[] = "%Y%m%d";
const tFields HK_IntraTemplate[] = { Date, Ignore, High, Low, Close, Volume, Invalid };
const char HK_DateTemplateIntra[] = "%Y%m%d%H%M";


BOOL CSpeedOrderDoc::ParseBufferTicker(char * parsebuffer, DWORD fileLength)
{
	PriceNodeVec tempNodes;         // if infile is sorted in the wrong order
	PriceNode Node;
	DWORD fileindex = 0;

	// skip first header line
	while (parsebuffer[fileindex] != 0x0a)
		fileindex++;
	parsebuffer[fileindex++] = '\0';
	while (iswspace(parsebuffer[fileindex]))
		fileindex++;

	const char* pDateTemplate = YahooDateTemplate2;
	const tFields *pLineTemplate = YahooTemplate;
	char delimiter = ',';

	enum { Yahoo1, Yahoo2, HK_DAY, HK_INTRA } FileType = Yahoo2;
	// TODO: get the line field template from the first header line in the input file
	if (strstr(parsebuffer, "Close*") != NULL)      // current format returned from Yahoo?
	{
		FileType = Yahoo1;
		pDateTemplate = YahooDateTemplate1;
	}
	else
		if (strstr(parsebuffer, "time;isin;high;low;last;vol#;vol$;open") != NULL)
		{
			FileType = HK_DAY;
			pLineTemplate = HK_DayTemplate;
			pDateTemplate = HK_DateTemplateDay;
			delimiter = ';';
		}
		else
			if (strstr(parsebuffer, "time;isin;high;low;last;vol#") != NULL)
			{
				FileType = HK_INTRA;
				pLineTemplate = HK_IntraTemplate;
				pDateTemplate = HK_DateTemplateIntra;
				delimiter = ';';
			}

	while (fileindex < fileLength)
	{
		char* pLine = &parsebuffer[fileindex];
		while (parsebuffer[fileindex] && parsebuffer[fileindex] != 0x0a && parsebuffer[fileindex] != 0x0d)
			++fileindex;
		parsebuffer[fileindex++] = '\0';
		while (iswspace(parsebuffer[fileindex]))
			fileindex++;
		Node.Clear();
		if (!ParseLineIntoNode(pLine, delimiter, pLineTemplate, pDateTemplate, Node))
			continue;

		Node.mPrice = Node.mLast;
		// Node.mPrice  = (HIGH + LOW) / 2;
		if (Node.mLow > 0.01 && Node.mPrice < Node.mLow)
			Node.mPrice = Node.mLow;
		else
			if (Node.mHigh > 0.01 && Node.mPrice > Node.mHigh)
				Node.mPrice = Node.mHigh;
		tempNodes.push_back(Node);
	} //while

	// in case the input-file was sorted in reverse order
	if (tempNodes.size() >= 2)
	{
		// check the order in the tempNode vector, to see if we need to go through it backwards or forwards
		// TODO: implement partial import/update check, so we mark the price nodes that are "new"
		//       to avoid affecting all old analysis not affected by the new data
		if (tempNodes.front().mDate > tempNodes.back().mDate)
		{
			PriceNodeVec::const_iterator it = tempNodes.end();
			while (it != tempNodes.begin())
				m_PriceNodesRaw.push_back(*(--it));
		}
		else
		{
			PriceNodeVec::const_iterator it = tempNodes.begin();
			while (it != tempNodes.end())
				m_PriceNodesRaw.push_back(*(it++));
		}
	}
	return TRUE;
}

BOOL CSpeedOrderDoc::WriteMonoWaves()
{
	FILE * DosStream;
	float  startPrice;
	float  endPrice;

	CString s = GetPathName();
	RemoveSuffix(s);
	s += "monowaves.txt";
	DosStream = fopen(s, "w+");
	fprintf(DosStream, "Startdate Nodes             prices           m0start    m2end  RetracementRule,Condition(,category)\n");

	for (size_t n = 0; n < m_MonoWaves.size(); n++)
	{
		startPrice = m_MonoWaves[n].StartPrice();
		endPrice = m_MonoWaves[n].EndPrice();

		fprintf(DosStream, "%08d  %6d - %6d   %5.2f - %5.2f   %6d / %6d  %d %c (%d)\n",
			m_MonoWaves[n].StartDate().GetYMD(),
			m_MonoWaves[n].mStartIndex,
			m_MonoWaves[n].mEndIndex,
			startPrice,
			endPrice,
			m_MonoWaves[n].m0StartIndex,
			m_MonoWaves[n].m2EndIndex,
			(int)m_MonoWaves[n].mRetracementRule,
			m_MonoWaves[n].mCondition,
			(int)m_MonoWaves[n].mCategory);
	}

	fclose(DosStream);
#ifdef NO_CHART
	AfxMessageBox("An output file has been produced: \n" + s);
#endif
	return TRUE;
}
